﻿/**
 * Peter
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 *
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 *
 *  This code is provided on an AS IS basis, with no WARRANTIES,
 *  CONDITIONS or GUARANTEES of any kind.
 *
 **/

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
using System.Windows.Input;
using Peter.Common;
using Peter.Common.Controls;
using Peter.Common.Dialog;
using Peter.Common.Interfaces;
using Peter.Common.MainMenu;
using Peter.Common.Services;
using Peter.Common.Utilities;
using Peter.Data;
using Peter.Properties;

namespace Peter.Logic
{
   /// <summary>
   /// Model for the file explorer.
   /// </summary>
   [Export (typeof (ModelFileExplorer))]
   [PartCreationPolicy (CreationPolicy.Shared)]
   public sealed class ModelFileExplorer : EditablePeterToolWindowWithMenu, IPartImportsSatisfiedNotification
   {
      private string m_SelectedFilePath;
      private FileExplorerOptions m_Options;
      private ExplorerItem m_SelectedItem;
      private RelayCommand m_CmdRename;
      private RelayCommand m_CmdFindFile;
      private RelayCommand m_CmdRunProcess;
      private RelayCommand m_CmdProperties;
      private RelayCommand m_CmdCreateFile;
      private RelayCommand m_CmdCreateFolder;
      private IEnumerable <ExplorerItem> m_FileSystem;

      /// <summary>
      /// Initializes a new file explorer model.
      /// </summary>
      [ImportingConstructor]
      public ModelFileExplorer (ITextInputRequestService textInput)
      {
         this.IsComposed = true;
         this.Title = "File Explorer";
         this.TextInputRequest = textInput;
         this.Icon = MainMenuIcon.Foldertree;
         this.ContentId = Guids.FILE_EXPLORER;
         this.ToolTip = "View the contents of your local hard drive";
      }

      /// <summary>
      /// Gets the text input request service.
      /// </summary>
      public ITextInputRequestService TextInputRequest { get; private set; }

      /// <summary>
      /// Gets the options.
      /// </summary>
      [Import (typeof (IPeterOptions))]
      public IPeterOptions PeterOptions { get; set; }

      /// <summary>
      /// Gets the command to create a new file.
      /// </summary>
      public ICommand CreateFileCommand
      {
         get { return this.m_CmdCreateFile ?? (this.m_CmdCreateFile = new RelayCommand (this.CreateFile, this.CanCreateFile)); }
      }

      /// <summary>
      /// Gets the command to create a new folder.
      /// </summary>
      public ICommand CreateFolderCommand
      {
         get { return this.m_CmdCreateFolder ?? (this.m_CmdCreateFolder = new RelayCommand (this.CreateFolder, this.CanCreateFile)); }
      }

      /// <summary>
      /// Gets the command to rename and item.
      /// </summary>
      public ICommand RenameCommand
      {
         get { return this.m_CmdRename ?? (this.m_CmdRename = new RelayCommand (this.OnRename, this.CanRename)); }
      }

      /// <summary>
      /// Gets the run process command.
      /// </summary>
      public ICommand RunProcessCommand
      {
         get
         {
            return this.m_CmdRunProcess ?? (this.m_CmdRunProcess = new RelayCommand (
               delegate { Process.Start (this.SelectedItem.File.FullName); },
               delegate { return this.SelectedItem != null; }));
         }
      }

      /// <summary>
      /// Gets the properties command.
      /// </summary>
      public ICommand PropertiesCommand
      {
         get
         {
            return this.m_CmdProperties ?? (this.m_CmdProperties = new RelayCommand (
               delegate { CommonUtilities.ShowFileProperties (this.SelectedItem.File.FullName); },
               delegate { return this.SelectedItem != null; }));
         }
      }

      /// <summary>
      /// Gets the find file command.
      /// </summary>
      public ICommand FindFileCommand
      {
         get { return m_CmdFindFile ?? (m_CmdFindFile = new RelayCommand (OnFindFile, CanFindFile)); }
      }

      /// <summary>
      /// Checks to see if the rename command can execute.
      /// </summary>
      /// <param name="obj">Command Parameter.</param>
      /// <returns>If the command can execute or not.</returns>
      private bool CanRename (object obj)
      {
         return this.SelectedItem != null &&
            (this.SelectedItem.File.Attributes & FileAttributes.ReadOnly) == 0 &&
            (this.SelectedItem.File.Attributes & FileAttributes.Hidden) == 0;
      }

      /// <summary>
      /// Renames the currently selected item.
      /// </summary>
      /// <param name="obj"></param>
      private void OnRename (object obj)
      {
         var title = "Rename " + (this.SelectedItem.IsDirectory ? "Directory" : "File");
         var prompt = string.Format ("What would you like to rename '{0}' to?{1}",
            this.SelectedItem.File.Name, this.SelectedItem.IsDirectory ? string.Empty : "\nPlease include the extension.");
         var newName = this.TextInputRequest.Request (prompt, title, this.IsPathValid, "New name");
         if (!string.IsNullOrWhiteSpace (newName)) {
            this.MoveSelectedItem (newName);
         }
      }

      /// <summary>
      /// Move (or renames) the selected item.
      /// </summary>
      /// <param name="newName">The new name of the selected item.</param>
      private void MoveSelectedItem (string newName)
      {
         if (this.SelectedItem.IsDirectory)
            MoveDirectory (newName);
         else
            MoveFile (newName);
         this.SelectedItem.Parent.RefreshDynamicLoad ();
      }

      /// <summary>
      /// Moves or renames a directory.
      /// </summary>
      /// <param name="newName">The new name of the directory.</param>
      private void MoveDirectory (string newName)
      {
         var directory = (DirectoryInfo) this.SelectedItem.File;
         var newPath = directory.Parent == null ? newName : Path.Combine (directory.Parent.FullName, newName);
         directory.MoveTo (newPath);
      }

      /// <summary>
      /// Moves or renames a file.
      /// </summary>
      /// <param name="newName">The new name of the file.</param>
      private void MoveFile (string newName)
      {
         var file = (FileInfo) this.SelectedItem.File;
         var oldPath = file.FullName;
         var newPath = file.Directory == null ? newName : Path.Combine (file.Directory.FullName, newName);
         file.MoveTo (newPath);
         this.Peter.FileMoved (oldPath, newPath);
      }

      /// <summary>
      /// Checks to see if a file or directory is valid.
      /// </summary>
      /// <param name="path">The relative path.</param>
      /// <returns>True if the path is valid.</returns>
      private bool IsPathValid (string path)
      {
         try {
            string newPath;
            if (this.SelectedItem.IsDirectory) {
               var directory = (DirectoryInfo)this.SelectedItem.File;
               newPath = directory.Parent == null ? path : Path.Combine (directory.Parent.FullName, path);
            } else {
               if (!path.Contains ("."))
                  return false;
               var file = (FileInfo)this.SelectedItem.File;
               newPath = file.Directory == null ? path : Path.Combine (file.Directory.FullName, path);
            }
            var fullPath = Path.GetFullPath (newPath);
            return !string.IsNullOrWhiteSpace (fullPath);
         } catch (Exception ex) {
            Console.Error.WriteLine ("'{0}' is not a valid name: {1}", path, ex.Message);
            return false;
         }
      }

      /// <summary>
      /// Checks if a new file can be created.
      /// </summary>
      /// <param name="obj">Command Parameter.</param>
      /// <returns>If the command can execute or not.</returns>
      private bool CanCreateFile (object obj)
      {
         return this.SelectedItem != null && this.SelectedItem.IsDirectory;
      }

      /// <summary>
      /// Prompts the user to create a new folder.
      /// </summary>
      /// <param name="obj">Command Parameter.</param>
      private void CreateFolder (object obj)
      {
         var directoryName = this.TextInputRequest.Request ("Please enter the name of the directory.", "New Directory",
            this.IsPathValid, "Directory name");
         if (!string.IsNullOrWhiteSpace(directoryName))
            this.CreateDirectory (directoryName);
      }

      /// <summary>
      /// Creates a new directory.
      /// </summary>
      /// <param name="directoryName">The name of the directory.</param>
      private void CreateDirectory (string directoryName)
      {
         var directory = this.SelectedItem.File.FullName;
         Directory.CreateDirectory (directory + "\\" + directoryName);
         this.SelectedItem.RefreshDynamicLoad ();
      }

      /// <summary>
      /// Prompts the user to create a new file.
      /// </summary>
      /// <param name="obj">Command Parameter.</param>
      private void CreateFile (object obj)
      {
         var fileName = this.TextInputRequest.Request ("Please enter the name of the file,\n including extension.", "New File",
            this.IsPathValid, "File name");
         if (!string.IsNullOrWhiteSpace (fileName))
            this.CreateFile (fileName);
      }

      /// <summary>
      /// Creates a new file.
      /// </summary>
      /// <param name="fileName">The name of the file.</param>
      private void CreateFile (string fileName)
      {
         var directory = this.SelectedItem.File.FullName;
         var filePath = directory + "\\" + fileName;
         File.Create (filePath).Dispose ();
         this.SelectedItem.RefreshDynamicLoad ();
         this.Peter.Open (filePath);
      }

      /// <summary>
      /// Gets or Sets the selected item.
      /// </summary>
      public ExplorerItem SelectedItem
      {
         get { return this.m_SelectedItem; }
         set
         {
            this.m_SelectedItem = value;
            this.OnPropertyChanged ("SelectedItem");
            this.UpdateMenuContext (this.SelectedItem);
         }
      }

      /// <summary>
      /// Checks whether an edit actions can be performed or not.
      /// </summary>
      /// <param name="obj">Edit action</param>
      /// <returns>True if action can be performed, otherwise false.</returns>
      protected override bool CanEdit (object obj)
      {
         if (this.SelectedItem == null || obj == null) return false;
         var editAction = (EditAction)obj;
         switch (editAction)
         {
            case EditAction.Paste:
               return this.SelectedItem.IsDirectory && ClipboardFiles.CanPaste ();
            case EditAction.Copy:
            case EditAction.Cut:
               return this.SelectedItem.Parent != null && !this.SelectedItem.IsDirectory;
            case EditAction.Delete:
               return this.SelectedItem.Parent != null;
            default:
               return false;
         }
      }

      /// <summary>
      /// Performs the edit action.
      /// </summary>
      /// <param name="obj">Edit action.</param>
      protected override void OnEdit (object obj)
      {
         var editAction = (EditAction)obj;
         switch (editAction)
         {
            case EditAction.Copy:
               ClipboardFiles.Copy (new [] { this.SelectedItem.File.FullName });
               break;
            case EditAction.Cut:
               ClipboardFiles.Cut (new[] { this.SelectedItem.File.FullName });
               this.SelectedItem.Parent.Children.Remove (this.SelectedItem);
               break;
            case EditAction.Paste:
               var pastedFiles = ClipboardFiles.Paste (this.SelectedItem.File.FullName);
               foreach (var pastedFile in pastedFiles)
               {
                  this.SelectedItem.Children.Add (new ExplorerItem (this.SelectedItem, new FileInfo (pastedFile)));
               }
               break;
            case EditAction.Delete:
               var message = new ModelMessageDialog
                                {
                                   Title = Resources.Delete,
                                   Tag = this.SelectedItem,
                                   Icon = MainMenuIcon.QuestionSign,
                                   ShowCancelButton = false,
                                   ShowOkButton = false,
                                   ShowNoButton = true,
                                   ShowYesButton = true,
                                   SubMessage = this.SelectedItem.File.FullName,
                                   Message = this.SelectedItem.File is DirectoryInfo ? Resources.DeleteDirectory : Resources.DeleteFile
                                };
               message.Closing += this.OnDeleteClosing;
               this.Peter.ShowDialog (message);
               break;
         }
      }

      /// <summary>
      /// Occurs when the delete message is closing.
      /// </summary>
      /// <param name="dialog"></param>
      private void OnDeleteClosing (DialogBase dialog)
      {
         dialog.Closing -= this.OnDeleteClosing;
         if (dialog.DialogCloseAction == DialogCloseAction.Yes)
         {
            var fileItem = dialog.Tag as ExplorerItem;
            if (fileItem != null)
            {
               var file = fileItem.File as FileInfo;
               if (file != null)
               {
                  file.Delete ();
                  fileItem.Parent.Children.Remove (fileItem);
               }
               else
               {
                  var dir = fileItem.File as DirectoryInfo;
                  if (dir != null)
                  {
                     dir.Delete (true);
                     fileItem.Parent.Children.Remove (fileItem);
                  }
               }
            }
         }
      }

      /// <summary>
      /// Gets or Sets the selected path of the file.
      /// </summary>
      public string SelectedFilePath
      {
         get { return m_SelectedFilePath; }
         set
         {
            this.m_SelectedFilePath = value;
            this.OnPropertyChanged ("SelectedFilePath");
         }
      }

      /// <summary>
      /// Gets or Sets the file system.
      /// </summary>
      public IEnumerable <ExplorerItem> FileSystem
      {
         get { return this.m_FileSystem; }
         set
         {
            this.m_FileSystem = value;
            if (this.m_Options != null)
               this.m_Options.Files = value;
         }
      }

      /// <summary>
      /// Checks to see if the find file command can execute.
      /// </summary>
      /// <param name="obj">The full path to the file.</param>
      /// <returns>True if the file exists, otherwise false.</returns>
      private static bool CanFindFile (object obj)
      {
         return obj != null && File.Exists (obj.ToString ());
      }

      /// <summary>
      /// Finds the given file in the explorer.
      /// </summary>
      /// <param name="obj">The full path to the file.</param>
      private void OnFindFile (object obj)
      {
         this.Peter.ShowToolWindow (PeterToolWindows.FileExplorer);
         this.SelectedFilePath = obj.ToString ();
      }

      /// <summary>
      /// Checks to see if the selected item can execute.
      /// </summary>
      /// <param name="obj">Command parameter.</param>
      /// <returns>True if command can execute, other wise false.</returns>
      protected override bool CanExecute (object obj)
      {
         var file = obj as FileSystemInfo;
         if (file != null)
            return file is FileInfo;

         if (this.SelectedItem != null)
            return !this.SelectedItem.IsDirectory;

         return false;
      }

      /// <summary>
      /// Executes the execute command.
      /// </summary>
      /// <param name="obj">Command Parameter.</param>
      protected override void OnExecute (object obj)
      {
         var file = obj as FileInfo;
         if (file != null)
         {
            this.Peter.Open (file.FullName);
         }

         if (this.SelectedItem != null)
            this.Peter.Open (this.SelectedItem.File.FullName);
      }

      /// <summary>Called when a part's imports have been satisfied and it is safe to use.</summary>
      public void OnImportsSatisfied ()
      {
         this.m_Options = this.PeterOptions.GetOptionData (FileExplorerOptions.FILE_EXPLORER_OPTION_NAME) as FileExplorerOptions;
         if (this.FileSystem != null && this.m_Options != null)
            this.m_Options.Files = this.FileSystem;
      }
   }
}
